using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Ares.PacketHandlers;

namespace cb0tServer
{
    public partial class ServerCore
    {
        private enum RejectionType : byte // user rejection scenarios
        {
            None = 0,
            RangeBan = 1,
            UserBan = 2,
            NameHijack = 3,
            JoinFilter = 4,
            JoinFlood = 5,
            ClientFlood = 6,
            SharedKick = 7,
            AnonKick = 8,
            AgeBan = 9,
            GenderBan = 10
        };

        private WebServer website;
        private List<String> banstats = new List<String>();
        private LinkClient link_client = new LinkClient();
        private RejectionType rejection_type = RejectionType.None;
      //  private String last_pm_spam = String.Empty;
        private TOR tor = new TOR();
        private TcpListener main_socket;
        private TcpListener scas_socket;
        private UdpClient udp_listener;
        private IPEndPoint udp_ep;
        private Thread main_thread;
        private bool terminate = false;

        private List<IPAddress> udp_firewall_checkers = new List<IPAddress>();
        private bool udp_got_servers = false;
        private bool udp_firewall_check_complete = false;
        private bool udp_firewall_is_testing = false;
        private int udp_firewall_test_start_time = 0;
        private int udp_firewall_index_check = 0;

        private int time_now = 0;

        public void Init() // start up the server
        {
            this.main_thread = new Thread(new ThreadStart(this.Work));
            this.main_thread.Start();
        }

        public void Kill() // shut down the server
        {
            this.terminate = true;
            Thread.Sleep(100); // give server thread a chance to expire

            try
            {
                this.main_socket.Stop();
                this.scas_socket.Stop();
            }
            catch { }

            UserPool.KillAllSessions();
            SCASPool.KillSessions();

            if (this.website != null)
                this.website.Die();

            this.link_client.Disconnect();

            if (this.udp_listener != null)
            {
                try
                {
                    this.udp_listener.Close();
                    this.udp_listener = null;
                }
                catch { }
            }

            this.QuizOn = false;
        }

        public void StartHttp()
        {
            this.website.Init(WebServerSettings.port);
        }

        public void StopHttp()
        {
            this.website.Die();
        }

        private void Work() // main server loop thread
        {
            for (int i = 0; i < 700; i++) UserPool.Users[i] = new UserObject(); // init session spaces
            for (int i = 0; i < 20; i++) SCASPool.scas_items[i] = new SCASObject(); // init session spaces

            this.banstats.Clear();
            this.website = new WebServer();

            // load files
            ChatHistory.Init();
            Greetings.Load();
            Settings.Load();
            NameHelpers.LoadCustomNames();
            AutoLogin.LoadAutoLogins();
            Bans.LoadBans();
            Captcha.LoadGuids();
            RangeBans.LoadBans();
            Whowas.Clear();
            Topics.LoadTopics();
            UrlTag.LoadUrlTag();
            WordFilters.LoadFilters();
            FileFilters.LoadFilters();
            JoinFilters.LoadFilters();
            PMFilters.LoadFilters();
            IdleMonitoring.Clear();
            Stats.Clear();
            MOTD.LoadMotd();
            Quotes.Load();
            StringTemplate.LoadTemplate();
            Muzzles.ClearMuzzles();
            Kiddys.ClearKiddys();
            Lowers.ClearLowers();

            this.udp_got_servers = this.LoadRooms();

            if (this.udp_got_servers)
                OtherLogs.WriteCacheLog("UDP GCache Loaded From Disk!");
            else
                this.DownloadServerListWorker();

            this.main_socket = new TcpListener(new IPEndPoint(IPAddress.Any, CoreSettings.room_port)); // listener

            try
            {
                this.main_socket.Start();
            }
            catch
            {
                System.Windows.Forms.MessageBox.Show("CRITICAL ERROR: UNABLE TO BIND PORT " + CoreSettings.room_port);
                return;
            }

            this.udp_ep = new IPEndPoint(CoreSettings.UDPAddress, CoreSettings.room_port);

            try
            {
                this.udp_listener = new UdpClient(this.udp_ep);
            }
            catch
            {
                System.Windows.Forms.MessageBox.Show("CRITICAL ERROR: UNABLE TO BIND UDP LISTENER");
                return;
            }

            this.scas_socket = new TcpListener(new IPEndPoint(IPAddress.Parse("127.0.0.1"), CoreSettings.room_port + 1)); // scas listener

            try
            {
                this.scas_socket.Start();
            }
            catch
            {
                System.Windows.Forms.MessageBox.Show("CRITICAL ERROR: UNABLE TO BIND PORT " + (CoreSettings.room_port + 1));
                return;
            }

            Thread.Sleep(1000);

            this.time_now = (int)cb0tServer.Helpers.UnixTime(); // update timestamp

            int timer_event_interval = this.time_now; // OnTimer tick counter
            int tor_update_interval = this.time_now - 17950; // tor updater
            int m_timeout_interval = this.time_now; // muzzle timeout checker
            int room_info_interval = this.time_now; // room info
            int udp_one_second = this.time_now;
            int udp_one_minute = this.time_now;
            int udp_15_minutes = this.time_now;
            int pm_spam_checker_tick = this.time_now;
            int dynamic_timer = this.time_now;
            int dns_timer = this.time_now;
            int last_cbans = this.time_now;
            int last_captcha = this.time_now;

            if (WebServerSettings.enabled) this.website.Init(WebServerSettings.port);

            while (true) // loop forever
            {
                this.time_now = (int)cb0tServer.Helpers.UnixTime(); // update timestamp

                if (this.terminate) return; // kill loop thread

                if (this.main_socket.Pending()) // client connections pending
                {
                    Socket _client = this.main_socket.AcceptSocket();
                    bool was_a_firewall_tester = false;

                    if (!this.udp_firewall_check_complete)
                    {
                        for (int x = 0; x < this.udp_firewall_checkers.Count; x++)
                        {
                            if (this.udp_firewall_checkers[x].Equals(((IPEndPoint)_client.RemoteEndPoint).Address))
                            {
                                was_a_firewall_tester = true;
                                this.udp_firewall_check_complete = true;
                                this.udp_firewall_checkers.Clear();
                                OtherLogs.WriteCacheLog("TCP Firewall Test Passed! :-)");

                                try
                                {
                                    _client.Disconnect(false);
                                }
                                catch { }

                                break;
                            }
                        }
                    }

                    if (!was_a_firewall_tester)
                    {
                        int _id = UserPool.GetNextAvailableSocketId();

                        if (_id > -1) // socket available
                        {
                            UserPool.Users[_id] = new UserObject(_client, _id, this.time_now);
                            UserPool.Users[_id].last_update_time = this.time_now;
                        }
                        else // unlikely scenario, over 700 sockets connected
                        {
                            _client.Disconnect(false);
                        }
                    }
                }

                if (this.scas_socket.Pending()) // client connections pending
                {
                    Socket _scas_client = this.scas_socket.AcceptSocket();
                    int _scas_id = SCASPool.GetNextAvailableSocketId();

                    if (_scas_id > -1 && ((IPEndPoint)_scas_client.RemoteEndPoint).Address.Equals(IPAddress.Parse("127.0.0.1"))) // socket available
                    {
                        SCASPool.scas_items[_scas_id] = new SCASObject(_scas_client, _scas_id, this.time_now);
                    }
                    else // over 20 sockets connected
                    {
                        _scas_client.Disconnect(false);
                    }
                }

                this.ServiceClientSockets(); // send outbound data and check for inbound data
                this.ServiceSCASSockets(); // send outbound data and check for inbound data

                if (this.link_client.IsSet())
                    this.ServiceLinkSocket(); // send outbound data and check for inbound data

                if (CoreSettings.show_on_list)
                    if (this.udp_got_servers)
                        this.ServiceUDPListener(); // send outbound data and check for inbound data

                if (this.time_now > (dynamic_timer + CoreSettings.dint))
                {
                    dynamic_timer = this.time_now;
                    this.OnDynamicTimer();
                }

                if (this.time_now > (dns_timer + 1)) // 2 seconds per check
                {
                    dns_timer = this.time_now;

                    foreach (UserObject _dns in UserPool.Users)
                    {
                        if (_dns.IsSet())
                        {
                            if (_dns.logged_in)
                            {
                                if (!_dns.got_dns)
                                {
                                    if (!String.IsNullOrEmpty(_dns.dns_value))
                                    {
                                        _dns.got_dns = true;
                                        this.OnDnsAfter(_dns);
                                    }
                                }
                            }
                        }
                    }

                    this.QuizOnTimer();

                    if (CoreSettings.clear_ban_enabled)
                    {
                        int cban_total_seconds = CoreSettings.clear_ban_interval; // total hours
                        cban_total_seconds *= 60; // total minutes
                        cban_total_seconds *= 60; // total seconds

                        if ((last_cbans + cban_total_seconds) < this.time_now)
                        {
                            last_cbans = this.time_now;
                            Muzzles.ClearMuzzles();
                            Bans.ClearBans();
                            RangeBans.ClearBans();
                            Kiddys.ClearKiddys();
                            Lowers.ClearLowers();
                            PMessages.Clear();
                            UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket(StringTemplate.GetString(131)));
                        }
                    }
                }

                if (this.time_now > (timer_event_interval + 30)) // timer event fires every 30 seconds
                {
                    timer_event_interval = this.time_now;
                    this.OnTimer();
                }

                if (this.time_now > (tor_update_interval + 18000)) // update tor
                {
                    tor_update_interval = this.time_now;
                    this.tor.Update();
                }

                if (this.time_now > m_timeout_interval && Settings.mtimeout > 0) // check muzzles
                {
                    m_timeout_interval = this.time_now;
                    Muzzles.CheckTimeouts();
                }

                if (this.time_now > (room_info_interval + 1200)) // room info
                {
                    room_info_interval = this.time_now;
                    if (Settings.room_info) this.OnRoomInfo();
                }

                if (this.time_now > (last_captcha + 1800)) // captcha
                {
                    last_captcha = this.time_now;
                    Captcha.SaveGuids();
                }

                if (this.time_now > udp_one_second) // one second udp tick
                {
                    udp_one_second = this.time_now;

                    if (CoreSettings.show_on_list)
                    {
                        this.ExpireFirewallTests(); // get rid of old tests

                        if (this.udp_got_servers) // do nothing if we don't have a list
                        {
                            if (this.udp_firewall_check_complete) // finished checking firewall?
                            {
                                this.PushMyRoom();
                            }
                            else
                            {
                                this.CheckFirewall();
                            }
                        }
                    }
                }

                if (this.time_now > (udp_one_minute + 60)) // one minute udp tick
                {
                    udp_one_minute = this.time_now;
                    Helpers.ClearTextFloods();

                    if (CoreSettings.show_on_list)
                        if (this.udp_got_servers)
                            this.ExpireList();
                }

                if (this.time_now > (udp_15_minutes + 900)) // 15 minute udp tick
                {
                    udp_15_minutes = this.time_now;

                    if (CoreSettings.show_on_list)
                    {
                        if (this.udp_got_servers)
                        {
                            if (this.udp_firewall_check_complete)
                            {
                                this.SaveRooms(false);
                            }
                            else
                            {
                                OtherLogs.WriteCacheLog("TCP firewall test failed or no servers were available to perform test!");
                                this.udp_firewall_check_complete = true;
                            }
                        }
                        else
                        {
                            this.DownloadServerListWorker();
                        }
                    }
                }

                Thread.Sleep(40); // reduce cpu usage
            }
        }

        private void ServiceLinkSocket()
        {
            if (this.link_client.mode == LinkClient.LinkMode.Client) // ensure in client mode that we've connected to the server
            {
                if (!this.link_client.Connected) // check connected yet?
                {
                    if (this.time_now > (this.link_client.session_spawn_time + 15)) // unable to connect
                    {
                        UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314link error: unable to connect!"));
                        this.link_client.Disconnect();
                        return;
                    }

                    if (this.link_client.socket.Poll(0, SelectMode.SelectWrite)) // connected :-)
                    {
                        this.link_client.Connected = true;
                        this.link_client.session_spawn_time = this.time_now; // reset spawn time for next check
                    }
                }
            }

            if (this.link_client.Connected) // connection established
            {
                this.link_client.DispatchOutboundPackets(); // send outbound

                if (!this.link_client.logged_in)
                {
                    if (this.time_now > (this.link_client.session_spawn_time + 15)) // taking too long to login
                    {
                        UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314link error: taking too long to handshake!"));
                        UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: disconnected"));
                        this.link_client.Disconnect();
                        return;
                    }
                }

                if (this.time_now > (this.link_client.last_update_received_time + 180)) // has linked room pinged us?
                {
                    this.link_client.PartAndDisconnect();
                    UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314link error: ping timeout!"));
                    UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: disconnected"));
                    return;
                }

                if (this.time_now > (this.link_client.last_update_sent_time + 60)) // have we pinged linked room?
                {
                    this.link_client.last_update_sent_time = this.time_now;
                    this.link_client.SendOutboundPacket(ServerOutboundPackets.LinkPongPacket());
                }

                // check inbound

                byte[] buffer = new byte[8192];
                SocketError e = SocketError.Success;
                int bytes_received = 0;

                try
                {
                    int avail = this.link_client.socket.Available;

                    if (avail > 8192)
                        avail = 8192;

                    bytes_received = this.link_client.socket.Receive(buffer,
                        0, avail, SocketFlags.None, out e);
                }
                catch { }

                if (bytes_received == 0) // check connection is still healthy
                {
                    if (e == SocketError.WouldBlock)
                    {
                        this.link_client.zeros = 0;
                    }
                    else
                    {
                        if (this.link_client.zeros++ > 3) // connection is dead
                        {
                            this.link_client.PartAndDisconnect();
                            UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314link error: linked room connection lost!"));
                            UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: disconnected"));
                            return;
                        }
                    }
                }
                else // we have new data received
                {
                    this.link_client.zeros = 0;
                    byte[] temp = new byte[bytes_received];
                    Array.Copy(buffer, 0, temp, 0, temp.Length);
                    this.link_client.AddReceivedBytes(temp);
                    Stats.BytesReceived += (ulong)bytes_received;
                }

                buffer = this.link_client.GetReceivedBytes();

                if (buffer != null) // possibly enough data to make a packet
                {
                    ushort packet_length = BitConverter.ToUInt16(buffer, 0);
                    byte packet_id = buffer[2];

                    if (buffer.Length >= (packet_length + 3)) // enough data is available
                    {
                        byte[] temp = new byte[packet_length];
                        Array.Copy(buffer, 3, temp, 0, temp.Length);
                        this.link_client.RemoveReceivedBytes(packet_length + 3);

                        if (packet_id == 80)
                        {
                            temp = ZLib.Zlib.Decompress(temp, false);
                            this.link_client.inbound_buffer.InsertRange(0, temp);
                        }
                        else if (packet_length <= 4096)
                        {
                            try
                            {
                                this.ProcessLinkPacket(packet_id, new AresDataPacket(temp));
                            }
                            catch (Exception emess) // badly formed packet
                            {
                                this.link_client.PartAndDisconnect();
                                UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314link error: malicious packet detected! [type " + packet_id + "]"));
                                UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: disconnected"));

                                UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("JIT: " + emess.Source + ": " + emess.Message));

                                StringBuilder sb = new StringBuilder();

                                foreach (byte __b in temp)
                                    sb.Append(__b + " ");

                                UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("packet data: " + sb.ToString()));

                                return;
                            }
                        }
                    }
                }
            }
        }

        private void ServiceSCASSockets()
        {
            foreach (SCASObject scas in SCASPool.scas_items)
            {
                if (scas.IsSet())
                {
                    if (!scas.logged_in) // give scas 15 seconds to log in
                    {
                        if (this.time_now > (scas.session_spawn_time + 15))
                        {
                            scas.Disconnect();
                            continue;
                        }
                    }
                    else // scas is logged in so send pending outbound
                    {
                        scas.DispatchOutboundPackets();
                    }

                    byte[] buffer = new byte[8192];
                    SocketError e = SocketError.Success;
                    int bytes_received = 0;

                    try
                    {
                        bytes_received = scas.socket.Receive(buffer, 0,
                            scas.socket.Available, SocketFlags.None, out e);
                    }
                    catch { }

                    if (bytes_received == 0) // check connection still exists
                    {
                        if (e == SocketError.WouldBlock)
                        {
                            scas.receive_fails = 0;
                        }
                        else
                        {
                            if (scas.receive_fails++ > 2) // user disconnected from server
                            {
                                scas.Disconnect();
                                continue;
                            }
                        }
                    }
                    else // data received, add to inbound dump
                    {
                        scas.receive_fails = 0;
                        byte[] temp = new byte[bytes_received];
                        Array.Copy(buffer, 0, temp, 0, temp.Length);
                        scas.AddReceivedBytes(temp);
                    }

                    buffer = scas.GetReceivedBytes();

                    if (buffer != null) // possibly enough data to make a packet
                    {
                        ushort packet_length = BitConverter.ToUInt16(buffer, 0);

                        if (packet_length > 4096) // malicious packet detected
                        {
                            scas.Disconnect();
                            continue;
                        }

                        byte packet_id = buffer[2];

                        if (buffer.Length >= (packet_length + 3)) // enough data is available
                        {
                            byte[] temp = new byte[packet_length];
                            Array.Copy(buffer, 3, temp, 0, temp.Length);
                            scas.RemoveReceivedBytes(packet_length + 3);

                            try
                            {
                                this.ProcessReceivedSCASPacket(scas, packet_id, new AresDataPacket(temp));
                            }
                            catch // badly formed packet
                            {
                                scas.Disconnect();
                            }
                        }
                    }
                }
            }
        }

        private void ServiceClientSockets()
        {
            foreach (UserObject user in UserPool.Users)
            {
                if (user.IsSet())
                {
                    if (!user.logged_in) // give users 15 seconds to log in
                    {
                        if (this.time_now > (user.session_spawn_time + 15))
                        {
                            user.Disconnect();
                            continue;
                        }
                    }
                    else // user is logged in so send pending outbound
                    {
                        user.DispatchOutboundPackets();
                    }

                    if (this.time_now > (user.last_update_time + 300) && user.logged_in) // give users 5 minutes to update
                    {
                        user.Disconnect();
                        this.OnPart(user);
                        Stats.UsersParted++;
                    }

                    byte[] buffer = new byte[8192];
                    SocketError e = SocketError.Success;
                    int bytes_received = 0;

                    try
                    {
                        int avail = user.socket.Available;

                        if (avail > 8192)
                            avail = 8192;

                        bytes_received = user.socket.Receive(buffer, 0,
                            avail, SocketFlags.None, out e);
                    }
                    catch { }

                    if (bytes_received == 0) // check connection still exists
                    {
                        if (e == SocketError.WouldBlock)
                        {
                            user.receive_fails = 0;
                        }
                        else
                        {
                            if (user.receive_fails++ > 2) // user disconnected from server
                            {
                                if (user.logged_in)
                                {
                                    user.Disconnect();
                                    this.OnPart(user);
                                    Stats.UsersParted++;
                                }
                                else
                                {
                                    user.Disconnect();
                                }
                                continue;
                            }
                        }
                    }
                    else // data received, add to inbound dump
                    {
                        user.receive_fails = 0;
                        byte[] temp = new byte[bytes_received];
                        Array.Copy(buffer, 0, temp, 0, temp.Length);
                        user.AddReceivedBytes(temp);
                        Stats.BytesReceived += (ulong)bytes_received;
                    }

                    buffer = user.GetReceivedBytes();

                    if (buffer != null) // possibly enough data to make a packet
                    {
                        ushort packet_length = BitConverter.ToUInt16(buffer, 0);

                        if (packet_length > 8192) // malicious packet detected
                        {
                            if (user.logged_in)
                            {
                                user.Disconnect();
                                this.OnPart(user);
                                Stats.UsersParted++;
                            }
                            else
                            {
                                user.Disconnect();
                            }

                            continue;
                        }

                        byte packet_id = buffer[2];

                        if (buffer.Length >= (packet_length + 3)) // enough data is available
                        {
                            byte[] temp = new byte[packet_length];
                            Array.Copy(buffer, 3, temp, 0, temp.Length);
                            user.RemoveReceivedBytes(packet_length + 3);

                            if (packet_id == 2 && user.logged_in) // stupid people
                            {
                                Bans.AddBan(user);
                                user.Disconnect();
                                this.OnPart(user);
                                Stats.UsersParted++;
                            }
                            else
                            {
                                if (user.EncMode == EncryptionMode.Encrypted)
                                    temp = ServerOutboundPackets.Decrypt(temp, user.EncByte, user.EncWord, (ProtoMessage)packet_id);

                                try
                                {
                                    this.ProcessReceivedPacket(user, packet_id, new AresDataPacket(temp));
                                }
                                catch // badly formed packet
                                {
                                    if (user.logged_in)
                                    {
                                        user.Disconnect();
                                        this.OnPart(user);
                                        Stats.UsersParted++;
                                    }
                                    else
                                    {
                                        user.Disconnect();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

    }
}
